package it.tukano.treditor.engine;

import it.tukano.concurrent.SyncVar;
import it.tukano.fun.Function1;
import it.tukano.treditor.engine.geom.BoundingSphere;
import it.tukano.treditor.engine.geom.NTuple2;
import it.tukano.treditor.engine.geom.NTuple3;
import it.tukano.treditor.engine.scene.SceneElement;
import java.awt.EventQueue;

public abstract class EngineQueryInterface {
    
    static abstract class EdtCallback<PARAM, RESULT> implements Runnable {
        final SyncVar<PARAM> var = new SyncVar<>();
        final SyncVar<RESULT> res = new SyncVar<>();

        EdtCallback(PARAM p) {
            var.set(p);
        }
        
        public void run() {
            res.set(computeResult(var.get()));
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    EdtCallback.this.handleResult(res.get());
                }
            });
        }
        
        abstract RESULT computeResult(PARAM p);
        
        abstract void handleResult(RESULT get);
    }
    
    static abstract class EdtCallback1<PARAM, RESULT> extends EdtCallback<PARAM, RESULT> {
        private final Function1<RESULT, ?> callback;
        
        EdtCallback1(PARAM p, Function1<RESULT, ?> callback) {
            super(p);
            this.callback = callback;
        }

        @Override
        void handleResult(RESULT get) {
            callback.apply(get);
        }
    }
        
    public void getElementBoundingSphere(SceneElement e, Function1<BoundingSphere, ?> callback) {
        submitRunnableForExecutionInEngineThread(new EdtCallback1<SceneElement, BoundingSphere>(e, callback) {

            @Override
            BoundingSphere computeResult(SceneElement p) {
                return EngineQueryInterface.this.computeGlobalBoundingSphere(p);
            }
        });
    }
    
    public void pickElementAtScreenPoint(NTuple2 screenPoint, SceneElement root, Function1<SceneElement, ?> callback) {
        final SyncVar<SceneElement> rootVar = new SyncVar<>();
        rootVar.set(root);
        submitRunnableForExecutionInEngineThread(new EdtCallback1<NTuple2, SceneElement>(screenPoint, callback) {

            @Override
            SceneElement computeResult(NTuple2 p) {
                return pickElementAtScreenPoint(rootVar.get(), p);
            }
        });
        
    }
    
    /** Make it so that the given task is executed in the engine's thread */
    protected abstract void submitRunnableForExecutionInEngineThread(Runnable task);
    
    /** Given a scene element, compute and return the global bounding sphere of it. Executed in the engine's thread. */
    protected abstract BoundingSphere computeGlobalBoundingSphere(SceneElement e);
    
    /** Get the 3d element belonging to the root breanch, at the given screen point (in awt coordinate). Returns null if no element can be picked. */
    protected abstract SceneElement pickElementAtScreenPoint(SceneElement root, NTuple2 screenPoint);
}
